from passlib.context import CryptContext
from datetime import datetime, timedelta
from fastapi import Depends, HTTPException
from jose import jwt, JWTError
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session

from database import get_db
from models.user import User

from setting import SECRET_KEY, ALGORITHM

# 创建一个密码加密上下文，使用 bcrypt 算法
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def hash_password(password: str) -> str:
    """
    对明文密码进行哈希加密，返回加密后的字符串。
    使用 passlib 的 CryptContext，默认使用 bcrypt 算法。
    :param password: 用户输入的明文密码
    :return: 哈希后的密码，用于存数据库
    """
    return pwd_context.hash(password)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    验证明文密码是否匹配哈希密码。
    :param plain_password: 用户输入的明文密码
    :param hashed_password: 存在数据库中的哈希密码
    :return: 如果明文密码和哈希密码匹配，返回 True；否则返回 False
    """
    return pwd_context.verify(plain_password, hashed_password)


def create_access_token(data: dict, expires_delta: timedelta = None) -> str:
    """
    生成 JWT 访问令牌（Access Token）。
    在 payload 中加入过期时间（exp 声明），然后使用 SECRET_KEY 和 ALGORITHM 签名。
    :param data: 要包含在 payload 中的自定义数据（如 {"sub": username 或 user_id}）
    :param expires_delta: 可选，自定义过期时长；如果为 None，则默认 15 分钟
    :return: 签名后的 JWT 字符串
    """
    # 复制一份 data，避免修改外部传入的 dict
    to_encode = data.copy()
    # 计算过期时间：当前 UTC 时间 + expires_delta，或默认 15 分钟
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    # 将过期时间插入 payload，JWT 库会识别 exp 并在解码时校验
    to_encode.update({"exp": expire})
    # 编码并签名，返回 token
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


# OAuth2PasswordBearer 会告诉 FastAPI 如何在请求中提取 token
# tokenUrl 参数应该是提供登录颁发 token 的接口路径，例如 "login" 或 "/api/user/v1/login"
# 如果你的登录接口路径不是根路径下的 /login，建议修改为完整路径，例如 tokenUrl="/api/user/v1/login"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")


def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User:
    """
    依赖函数：从请求中获取并验证 JWT Token，然后返回对应的 User 对象。
    通常用于受保护路由中：Depends(get_current_user)。
    1. FastAPI 会从请求头 Authorization: Bearer <token> 中提取 token。
    2. 解码并校验 token（包括签名、过期时间等）。
    3. 从 payload 中取出用户名（sub 字段），再查询数据库获取 User。
    4. 如果任一步失败，都抛出 HTTPException，返回 401 未授权。
    :param token: 从 OAuth2PasswordBearer 提取的 JWT 字符串
    :param db: SQLAlchemy 会话，通过 get_db 依赖注入
    :return: 对应的 User 实例
    """
    try:
        # 解码 JWT token，自动校验签名和 exp（过期）等
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        # 通常在 payload 中使用 "sub" 字段存用户名或用户 ID
        username = payload.get("sub")
        if username is None:
            # 如果没有 sub 字段，说明 token 格式不对或不合法
            raise HTTPException(status_code=401, detail="无效 token：缺少 sub 字段")
    except JWTError:
        # 捕获所有 JWT 相关异常（如签名错误、过期等）
        raise HTTPException(status_code=401, detail="token 验证失败或已过期")

    # 根据用户名查询数据库，获取用户对象
    user_obj = db.query(User).filter(User.username == username).first()
    if not user_obj:
        # 如果用户不存在，说明 token 中的用户名无效或用户被删除
        raise HTTPException(status_code=401, detail="用户不存在或已被删除")
    return user_obj


def parse_token(token: str) -> dict | None:
    """
    解析 JWT token 并提取 user_id 或 sub。
    可用于 await 调用，保持调用风格一致。

    :param token: JWT token 字符串（不带 Bearer 前缀）
    :return:jwt，无效则返回 None
    """
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    except JWTError:
        return None
